/**
 * Captain
 * © 2017 Captain copyright，All rights reserved.
 * http://www.sccaptain.com.cn
 * 
 * JAVA : 8
 * 文  件  名: BaseJpaRepository.java
 * 创  建  人: Roc Zheng (roc.djz@gmail.com)
 * 创建时间: 2017年6月21日 下午7:14:46
 * 版         本: 1.0.0
 * 备         注:
 * 修订历史:
 */
package com.material.c2c.core.dao.datajpa;

import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.TypedQuery;

import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.JpaEntityInformationSupport;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import com.material.c2c.core.criteria.CriteriaHelper;
import com.material.c2c.core.dao.EntityDao;

/**
 * 
 * 
 * @since 1.0
 * @version 1.0
 * @author Roc Zheng (roc.djz@gmail.com)
 */
@Repository
@Transactional(readOnly = true)
public class BaseJpaRepository<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements EntityDao<T, ID> {
  protected final EntityManager entityManager;
  protected final JpaEntityInformation<T, ?> entityInformation;
  
  /**
   * 
   * @param domainClass
   * @param entityManager
   */
  public BaseJpaRepository(Class<T> domainClass, EntityManager entityManager) {
    this(JpaEntityInformationSupport.getEntityInformation(domainClass, entityManager), entityManager);
  }

  /**
   * @param entityInformation
   * @param entityManager
   */
  public BaseJpaRepository(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
    super(entityInformation, entityManager);
    
    this.entityManager = entityManager;
    this.entityInformation = entityInformation;
  }
  
  /**
   * 
   * @param entity
   * @return
   *
   * @since 1.0
   * @version 1.0
   * @author Roc Zheng (roc.djz@gmail.com)
   */
  @Transactional
  public <S extends T> S  add(S entity) {
    entityManager.persist(entity);
    return entity;
  }
  
  /**
   * 
   * @param entity
   *
   * @since 1.0
   * @version 1.0
   * @author Roc Zheng (roc.djz@gmail.com)
   */
  @Transactional
  public <S extends T> S  update(S entity) {
    return entityManager.merge(entity);
  }
  
  /**
   * {@inheritDoc}
   */
  @Override
  public T getOne(ID id) {
    return findOne(id);
  }
  
  /**
   * 
   * @return
   * 
   * @since 1.0
   * @version 1.0
   * @author Roc Zheng (roc.djz@gmail.com)
   */
  public List<T> listAll() {
    return super.findAll();
  }
  
  /**
   * 
   * @param ids
   * @return
   * 
   * @since 1.0
   * @version 1.0
   * @author Roc Zheng (roc.djz@gmail.com)
   */
  public List<T> listAll(Iterable<ID> ids) {
    return super.findAll(ids);
  }
  
  /**
   * 
   * @param sort
   * @return
   * 
   * @since 1.0
   * @version 1.0
   * @author Roc Zheng (roc.djz@gmail.com)
   */
  public List<T> listAll(Sort sort) {
    return super.findAll(sort);
  }
  
  /**
   * 
   * @param specification
   * @return
   * 
   * @since 1.0
   * @version 1.0
   * @author Roc Zheng (roc.djz@gmail.com)
   */
  public List<T> listAll(Specification<T> specification) {
    return super.findAll(specification);
  }
  
  /**
   * 
   * @param example
   * @return
   * 
   * @since 1.0
   * @version 1.0
   * @author Roc Zheng (roc.djz@gmail.com)
   */
  public <S extends T> List<S> listAll(Example<S> example) {
    return super.findAll(example);
  }
  
  /**
   * 
   * @param specification
   * @param sort
   * @return
   * 
   * @since 1.0
   * @version 1.0
   * @author Roc Zheng (roc.djz@gmail.com)
   */
  public List<T> listAll(Specification<T> specification, Sort sort) {
    return super.findAll(specification, sort);
  }
  
  /**
   * 
   * @param example
   * @param sort
   * @return
   * 
   * @since 1.0
   * @version 1.0
   * @author Roc Zheng (roc.djz@gmail.com)
   */
  public <S extends T> List<S> listAll(Example<S> example, Sort sort) {
    return super.findAll(example, sort);
  }
  
  /**
   * 
   * @param pageable
   * @return
   * 
   * @since 1.0
   * @version 1.0
   * @author Roc Zheng (roc.djz@gmail.com)
   */
  public Page<T> pageAll(Pageable pageable) {
    return super.findAll(pageable);
  }
  
  /**
   * 
   * @param specification
   * @param pageable
   * @return
   * 
   * @since 1.0
   * @version 1.0
   * @author Roc Zheng (roc.djz@gmail.com)
   */
  public Page<T> pageAll(Specification<T> specification, Pageable pageable) {
    return super.findAll(specification, pageable);
  }
  
  /**
   * 
   * @param example
   * @param pageable
   * @return
   * 
   * @since 1.0
   * @version 1.0
   * @author Roc Zheng (roc.djz@gmail.com)
   */
  public <S extends T> Page<S> pageAll(Example<S> example, Pageable pageable) {
    return super.findAll(example, pageable);
  }
  
  /**
   * 
   * @param hql
   * @param helper
   * @param pageable
   * @return
   * @since 1.0
   * @version 1.0
   * @author liutao (liu.tao@sccaptain.com.cn)
   */
  @SuppressWarnings("unchecked")
  public Page<T> pageAll(String hql,CriteriaHelper helper, Pageable pageable){
    
    // 查询shql
    StringBuffer shql = new StringBuffer(hql);
    //条件
    StringBuffer where = helper.getWhereClause();
    //排序
    StringBuffer order = helper.getOrderClause();
    //参数值
    List<Object> values = helper.getValueList();
    // 统计结果数  hql
    StringBuffer countHql = new StringBuffer();
    
    Query query = entityManager.createQuery(shql.append(where).append(order).toString(), this.getDomainClass());
    for(int i=0;i<values.size();i++){
      query.setParameter(i+1, values.get(i));
    }
    query.setFirstResult(pageable.getOffset());
    query.setMaxResults(pageable.getPageSize());
    List<T> list = query.getResultList();
    int index = hql.toLowerCase().indexOf("from");
    countHql.append("select ").append("count("+(helper.getDistinct()!=null?(helper.getDistinct()):1)+")").append(hql.substring(index)).append(where);
    Query query2 = entityManager.createQuery(countHql.toString());
    for(int i=0;i<values.size();i++){
      query2.setParameter(i+1, values.get(i));
    }
    long total = (long) query2.getSingleResult();
    Page<T> page = new PageImpl<>(list, null, total);
    return page;
  }
  
  public Page<T> pageByJpql(String jpql, Pageable pageable, Map<String, Object> params) {
    return pageByJpql(jpql, null, null, null, getDomainClass(), pageable, params);
  }
  
  public <S> Page<S> pageByJpql(String jpql, Class<S> resultClass, Pageable pageable, Map<String, Object> params) {
    return pageByJpql(jpql, null, null, null, resultClass, pageable, params);
  }
  
  public Page<T> pageByJpql(String jpql, String orderEntityAlias, Pageable pageable, Map<String, Object> params) {
    return pageByJpql(jpql, null, null, orderEntityAlias, getDomainClass(), pageable, params);
  }
  
  public <S> Page<S> pageByJpql(String jpql, String orderEntityAlias, Class<S> resultClass, Pageable pageable, Map<String, Object> params) {
    return pageByJpql(jpql, null, null, orderEntityAlias, resultClass, pageable, params);
  }
  
  public Page<T> pageByJpql(String jpql, String countDistinctProperty, String countDistinctEntityAlias, String orderEntityAlias, Pageable pageable, Map<String, Object> params) {
    return pageByJpql(jpql, countDistinctProperty, countDistinctEntityAlias, orderEntityAlias, getDomainClass(), pageable, params);
  }
  
  public <S> Page<S> pageByJpql(String jpql, String countDistinctProperty, String countDistinctEntityAlias, String orderEntityAlias, Class<S> resultClass, Pageable pageable, Map<String, Object> params) {
    TypedQuery<Long> countQuery = getCountQuery(jpql, countDistinctProperty, countDistinctEntityAlias);
    for (Entry<String, Object> entry : params.entrySet()) {
      countQuery.setParameter(entry.getKey(), entry.getValue());
    }
    long total = executeCountQuery(countQuery);
    
    TypedQuery<S> dataQuery = getQuery(jpql, resultClass, pageable, orderEntityAlias);
    for (Entry<String, Object> entry : params.entrySet()) {
      dataQuery.setParameter(entry.getKey(), entry.getValue());
    }
    dataQuery.setFirstResult(pageable.getOffset()).setMaxResults(pageable.getPageSize());
    
    return new PageImpl<>(dataQuery.getResultList(), pageable, total);
  }
  
  public Page<T> pageByJpql(String jpql, Pageable pageable, Object... params) {
    return pageByJpql(jpql, null, null, null, getDomainClass(), pageable, Arrays.asList(params));
  }
  
  public <S> Page<S> pageByJpql(String jpql, Class<S> resultClass, Pageable pageable, Object... params) {
    return pageByJpql(jpql, null, null, null, resultClass, pageable, Arrays.asList(params));
  }
  
  public Page<T> pageByJpql(String jpql, String orderEntityAlias, Pageable pageable, Object... params) {
    return pageByJpql(jpql, null, null, orderEntityAlias, getDomainClass(), pageable, Arrays.asList(params));
  }
  
  public <S> Page<S> pageByJpql(String jpql, String orderEntityAlias, Class<S> resultClass, Pageable pageable, Object... params) {
    return pageByJpql(jpql, null, null, orderEntityAlias, resultClass, pageable, Arrays.asList(params));
  }
  
  public Page<T> pageByJpql(String jpql, String orderEntityAlias, Pageable pageable, Iterable<Object> params) {
    return pageByJpql(jpql, null, null, orderEntityAlias, getDomainClass(), pageable, params);
  }
  
  public <S> Page<S> pageByJpql(String jpql, String orderEntityAlias, Class<S> resultClass, Pageable pageable, Iterable<Object> params) {
    return pageByJpql(jpql, null, null, orderEntityAlias, resultClass, pageable, params);
  }
  
  public <S> Page<S> pageByJpql(String jpql, String countDistinctProperty, String countDistinctEntityAlias, String orderEntityAlias, Class<S> resultClass, Pageable pageable, Iterable<Object> params) {
    TypedQuery<Long> countQuery = getCountQuery(jpql, countDistinctProperty, countDistinctEntityAlias);
    int paramIndex = 0;
    for (Object param : params) {
      countQuery.setParameter(paramIndex++, param);
    }
    long total = executeCountQuery(countQuery);
    
    TypedQuery<S> dataQuery = getQuery(jpql, resultClass, pageable, orderEntityAlias);
    paramIndex = 0;
    for (Object param : params) {
      dataQuery.setParameter(paramIndex++, param);
    }
    dataQuery.setFirstResult(pageable.getOffset()).setMaxResults(pageable.getPageSize());
    
    return new PageImpl<S>(dataQuery.getResultList(), pageable, total);
  }
  
  protected <S> TypedQuery<S> getQuery(String jpql, Class<S> resultClass, Pageable pageable, String entityAlias) {
    Sort sort = pageable == null ? null : pageable.getSort();
    jpql = applySortToJqpl(jpql, sort, entityAlias);
    TypedQuery<S> dataQuery = entityManager.createQuery(jpql, resultClass);
    return dataQuery;
  }
  
  protected TypedQuery<Long> getCountQuery(String jpql) {
    return getCountQuery(jpql, null, null);
  }
  
  protected TypedQuery<Long> getCountQuery(String jpql, String distinctProperty) {
    return getCountQuery(jpql, distinctProperty, null);
  }
  
  protected TypedQuery<Long> getCountQuery(String jpql, String distinctProperty, String distinctEntityAlias) {
    StringBuilder sb = new StringBuilder("select count(");
    if (StringUtils.hasText(distinctProperty)) {
      sb.append("distinct ");
      if (StringUtils.hasText(distinctEntityAlias)) {
        sb.append(distinctEntityAlias).append(".");
      }
      sb.append(distinctProperty);
    } else {
      sb.append("1");
    }
    
    sb.append(") ").append(jpql.substring(jpql.toLowerCase().indexOf("from")));
    return entityManager.createQuery(sb.toString(), Long.class);
  }
  
  protected String applyStartsWith(String value) {
    return value + "%";
  }
  
  protected String applyContains(String value) {
    return "%" + value + "%";  
  }
  
  protected String applyEndsWith(String value) {
    return "%" + value;
  }
  
  private static String applySortToJqpl(String jpql, Sort sort, String entityAlias) {
    if (null != sort) {
      StringBuilder sb = new StringBuilder(jpql).append(" order by");
      boolean first = true;
      for (Sort.Order order : sort) {
        if (!first) {
          sb.append(",");
        }
        
        sb.append(" ");
        if (StringUtils.hasText(entityAlias)) {
          sb.append(entityAlias).append(".");
        }
        sb.append(order.getProperty());
        if (Direction.DESC.equals(order.getDirection())) {
          sb.append(" desc");
        }
      }
      jpql = sb.toString();
    }
    
    return jpql;
  }
  
  private static Long executeCountQuery(TypedQuery<Long> query) {
    Assert.notNull(query, "query 不能为空。");
    
    List<Long> totals = query.getResultList();
    Long total = 0L;

    for (Long element : totals) {
      total += element == null ? 0 : element;
    }

    return total;
  }
  
  @SuppressWarnings("unchecked")
  public List<T> listAll(String hql,CriteriaHelper helper){
    // 查询shql
    StringBuffer shql = new StringBuffer(hql);
    //条件
    StringBuffer where = helper.getWhereClause();
    //排序
    StringBuffer order = helper.getOrderClause();
    //参数值
    List<Object> values = helper.getValueList();
    Query query = entityManager.createQuery(shql.append(where).append(order).toString(), this.getDomainClass());
    for(int i=0;i<values.size();i++){
      query.setParameter(i+1, values.get(i));
    }
    return query.getResultList();
  }
} 
